﻿using System;
using System.ComponentModel;

namespace Needle.Utils.Extensions
{
    /// <summary>
    /// Func extension methods
    /// </summary>
    public static class FuncExtensions
    {
        /// <summary>
        /// Dispatches the function and result actions synchronously to a
        /// SynchronizationContext
        /// </summary>
        public static void Send<T, TResult>(this Func<T, TResult> func, T item, Action<TResult> onSucces, Action<Exception> onError)
        {
            if (func == null)
                throw new NullReferenceException();
            AsyncFunc<T, TResult> asyncFunc = new AsyncFunc<T, TResult>(func, item, onSucces, onError);
            asyncFunc.Send();
        }

        /// <summary>
        /// Dispatches the function and result actions asynchronously to a
        /// SynchronizationContext
        /// </summary>
        public static void Post<T, TResult>(this Func<T, TResult> func, T arg, Action<TResult> onSucces, Action<Exception> onError)
        {
            if (func == null)
                throw new NullReferenceException();
            AsyncFunc<T, TResult> asyncFunc = new AsyncFunc<T, TResult>(func, arg, onSucces, onError);
            asyncFunc.Post();
        }

        /// <summary>
        /// Dispatches the function asynchonously to the threadpool and result actions to a
        /// SynchronizationContext
        /// </summary>
        public static void BeginInvoke<T, TResult>(this Func<T, TResult> func, T arg, Action<TResult> onSucces, Action<Exception> onError)
        {
            if (func == null)
                throw new NullReferenceException();
            AsyncFunc<T, TResult> asyncFunc = new AsyncFunc<T, TResult>(func, arg, onSucces, onError);
            asyncFunc.BeginInvoke();
        }

        private class AsyncFunc<T, TResult>
        {
            #region constructors

            /// <summary>
            /// Initializes a new instance of the AsyncFunc class.
            /// </summary>
            public AsyncFunc(Func<T, TResult> func , T arg, Action<TResult> onSuccess, Action<Exception> onError)
            {
                this.Arg = arg;
                this.Func = func;
                this.OnError = onError;
                this.OnSuccess = onSuccess;
                this.AsyncOperation = AsyncOperationManager.CreateOperation(this);
            }

            #endregion

            #region private fields...

            private T Arg;
            private TResult Result;
            private Exception Error;
            private Func<T, TResult> Func;
            private Action<Exception> OnError;
            private Action<TResult> OnSuccess;
            private AsyncOperation AsyncOperation;

            #endregion

            #region public methods...

            /// <summary>
            /// runs the func and the result actions in 
            /// the current SynchronizationContext synchronously.
            /// </summary>
            public void Send()
            {
                AsyncOperation.SynchronizationContext.Send(SendFunc, this);
            }

            /// <summary>
            /// runs the func and the result actions in 
            /// the current SynchronizationContext asynchronously.
            /// </summary>
            public void Post()
            {
                AsyncOperation.Post(PostFunc, this);
            }

            /// <summary>
            /// runs the func asynchronously on the threadpool  
            /// and runs the result actions in 
            /// the current SynchronizationContext asynchronously.
            /// </summary>
            public void BeginInvoke()
            {
                Func.BeginInvoke(Arg, EndInvoke, this);
            }

            #endregion

            private static void EndInvoke(IAsyncResult result)
            {
                var context = result.AsyncState as AsyncFunc<T, TResult>;
                try
                {
                    context.Result = context.Func.EndInvoke(result);
                    if (context.OnSuccess != null)
                        context.AsyncOperation.PostOperationCompleted(PostSuccess, context);
                    else
                        context.AsyncOperation.OperationCompleted();
                }
                catch (Exception ex)
                {
                    context.Error = ex;
                    if (context.OnError != null)
                        context.AsyncOperation.PostOperationCompleted(PostError, context);
                    else
                        context.AsyncOperation.OperationCompleted();
                }
                 
            }
            private static void SendFunc(object asyncFunc)
            {
                var context = (AsyncFunc<T, TResult>)asyncFunc;
                try
                {
                    context.Result = context.Func(context.Arg);
                    if (context.OnSuccess != null)
                        PostSuccess(context);
                }
                catch (Exception ex)
                {
                    context.Error = ex;
                    if (context.OnError != null)
                        PostError(context);
                }
                finally
                {
                    context.AsyncOperation.OperationCompleted();
                }
            }
            private static void PostFunc(object asyncFunc)
            {
                var context = (AsyncFunc<T, TResult>)asyncFunc;
                try
                {
                    context.Result = context.Func(context.Arg);
                    if (context.OnSuccess != null)
                        context.AsyncOperation.PostOperationCompleted(PostSuccess, context);
                    else
                        context.AsyncOperation.OperationCompleted();
                }
                catch (Exception ex)
                {
                    context.Error = ex;
                    if (context.OnError != null)
                        context.AsyncOperation.PostOperationCompleted(PostError, context);
                    else
                        context.AsyncOperation.OperationCompleted();
                }
                
            }

            private static void PostSuccess(object asyncFunc)
            {
                try
                {
                    var context = (AsyncFunc<T, TResult>)asyncFunc;
                    context.OnSuccess(context.Result);
                }
                catch 
                { /* do nothing */ }
            }

            private static void PostError(object asyncFunc)
            {
                try
                {
                    var context = (AsyncFunc<T, TResult>)asyncFunc;
                    context.OnError(context.Error);
                }
                catch 
                { /* do nothing */ }
            }
        }
    }
}

